namespace Brjnk.GpxExplorer.Workspace.GCBrowser
{
    using System;
    using System.Collections.Generic;
    using Brjnk.Common;
    using Brjnk.GpxExplorer.Data;
    using mshtml;

    /// <summary>
    /// </summary>
    public class GcHtmlReader
    {
        public GcHtmlReaderResult Read(HTMLDocument doc)
        {
            try
            {
                return ReadInternal(doc);
            }
            catch (Exception)
            {
                return new GcHtmlReaderResult(ResultType.Failed, null);
            }
        }

        private GcHtmlReaderResult ReadInternal(HTMLDocument doc)
        {
            bool notLogged = IsNotLogged(doc);
            if (notLogged) return new GcHtmlReaderResult(ResultType.NotLoggedIn, null);
            HTMLSpanElement idSpan = GetSpan(doc, "ctl00_ContentBody_CoordInfoLinkControl1_uxCoordInfoCode");
            if ((object)idSpan == null) return new GcHtmlReaderResult(ResultType.NotGc, null);

            string id = GetInnerText(idSpan);
            HTMLSpanElement terrainSpan = GetSpan(doc, "ctl00_ContentBody_Localize12");
            HTMLSpanElement difficultySpan = GetSpan(doc, "ctl00_ContentBody_uxLegendScale");
            HTMLSpanElement sizeSpan = GetSpan(doc, "ctl00_ContentBody_size");
            HTMLDivElement hintDiv = GetDiv(doc, "div_hint");
            HTMLSpanElement nameSpan = GetSpan(doc, "ctl00_ContentBody_CacheName");
            HTMLDivElement authorDiv = GetDiv(doc, "ctl00_ContentBody_mcd1");
            HTMLAnchorElement typeA = GetTypeA(doc);
            HTMLSpanElement coordinatesSpan = GetSpan(doc, "uxLatLon");
            bool found = (object)doc.getElementById("ctl00_ContentBody_GeoNav_logText") != null;
            string htmlBody = GetGeocacheBody(doc);

            GeocacheInfoDefault info = new GeocacheInfoDefault();
            info.Author = ReadAuthor(authorDiv);
            info.Difficulty = ReadStarInsideSpan(difficultySpan);
            info.Terrain = ReadStarInsideSpan(terrainSpan);
            info.Size = ReadSize(sizeSpan);
            info.Name = ReadName(nameSpan);
            info.HtmlShort = htmlBody;
            info.Hint = ReadHint(hintDiv);
            info.Type = ReadType(typeA);

            info.Logs = new MVVM.ObservableCollectionEx<GeocacheLog>();
            info.Logs.Add(new GeocacheLogDefault() { Date = new DateTime(2000, 1, 1), Finder = "GpxExplorer", Text = "Logs have not been loaded.", Type = "Write note" });

            var coord = GpsCoordinates.Parse(ModifyCoordString(GetInnerText(coordinatesSpan)));
            Geocache gc = new Geocache(id, info, coord, found);
            return new GcHtmlReaderResult(ResultType.Ok, gc);
        }

        private string ModifyCoordString(string p)
        {
            p = p.Replace("E", "'E");
            p = p.Replace("W", "'W");
            return p + "'";
        }

        #region GetElement

        private HTMLAnchorElement GetTypeA(HTMLDocument doc)
        {
            var collection = doc.getElementsByTagName("a");
            for (int i = 0; i < collection.length; i++)
            {
                HTMLAnchorElement anchor = collection.item(i);
                if (anchor.href.EndsWith("cache_types.aspx", StringComparison.InvariantCultureIgnoreCase))
                    return anchor;
            }
            return null;
        }

        private HTMLSpanElement GetSpan(HTMLDocument doc, string id)
        {
            mshtml.HTMLSpanElement s = (HTMLSpanElement)doc.getElementById(id);
            return s;
        }

        private HTMLDivElement GetDiv(HTMLDocument doc, string id)
        {
            mshtml.HTMLDivElement s = (HTMLDivElement)doc.getElementById(id);
            return s;
        }

        private List<dynamic> GetElementsByClassName(HTMLDocument doc, string tagName, string className)
        {
            List<dynamic> retVal = new List<dynamic>();
            var collection = doc.getElementsByTagName(tagName);
            for (int i = 0; i < collection.length; i++)
            {
                var item = collection.item(i);
                if (item.className == className)
                    retVal.Add(collection.item(i));
            }
            return retVal;
        }

        private string GetGeocacheBody(HTMLDocument doc)
        {
            string result = string.Empty;
            var elements = GetElementsByClassName(doc, "div", "UserSuppliedContent");
            foreach (var e in elements)
            {
                result += e.innerHtml;
            }
            return result;
        }

        private string GetInnerText(object element)
        {
            dynamic dyn = (dynamic)element;
            return dyn.innerText;
        }

        private string GetInnerHtml(object element)
        {
            dynamic dyn = (dynamic)element;
            return dyn.innerHTML;
        }

        #endregion GetElement

        #region ReadElement

        private string ReadAuthor(HTMLDivElement authorDiv)
        {
            var collection = authorDiv.getElementsByTagName("a");
            if (collection.length != 1) throw new Exception();
            HTMLAnchorElement anchor = (HTMLAnchorElement)collection.item(0);
            return anchor.innerText;
        }

        private string Decode(string encoded)
        {
            var bytes = System.Text.Encoding.UTF32.GetBytes(encoded);
            for (int i = 0; i < bytes.Length; i += 4)
            {
                if (bytes[i] == 0 && bytes[i + 1] == 0 && bytes[i + 2] == 0)
                {
                    var b = bytes[i + 3];
                    if (b >= 97 && b <= 122) b = (byte)(122 - (b - 97));
                    if (b >= 65 && b <= 90) b = (byte)(90 - (b - 65));
                    bytes[i] = b;
                }
            }
            return System.Text.Encoding.UTF32.GetString(bytes);
        }

        private string ReadHint(HTMLDivElement hintDiv)
        {
            string encoded = hintDiv.innerText;
            if (encoded == null) return string.Empty;
            return Decode(encoded);
        }

        private GeocacheType? ReadType(HTMLAnchorElement typeA)
        {
            var innerHTML = typeA.innerHTML.ToLower();
            if (innerHTML.Contains("traditional")) return GeocacheType.Traditional;
            else if (innerHTML.Contains("unknown") || innerHTML.Contains("puzzle")) return GeocacheType.Puzzle;
            else if (innerHTML.Contains("multi")) return GeocacheType.Multi;
            else if (innerHTML.Contains("mega") && innerHTML.Contains("event")) return GeocacheType.MegaEvent;
            else if (innerHTML.Contains("event")) return GeocacheType.Event;
            else if (innerHTML.Contains("wherigo")) return GeocacheType.Wherigo;
            else if (innerHTML.Contains("earth")) return GeocacheType.EarthCache;
            else if (innerHTML.Contains("web") && innerHTML.Contains("cam")) return GeocacheType.Webcam;
            else return GeocacheType.Unknown;
        }

        private string ReadName(HTMLSpanElement nameSpan)
        {
            return nameSpan.innerText;
        }

        private GeocacheSize? ReadSize(HTMLSpanElement sizeSpan)
        {
            var collection = sizeSpan.getElementsByTagName("img");
            if (collection.length != 1) throw new Exception();
            HTMLImg img = (HTMLImg)collection.item(0);
            string text = img.src;
            return ReadContainerSize(img.href);
        }

        private GeocacheSize? ReadContainerSize(string imgSrc)
        {
            imgSrc = imgSrc.ToLower();
            if (imgSrc.Contains("micro")) return GeocacheSize.Micro;
            else if (imgSrc.Contains("small")) return GeocacheSize.Small;
            else if (imgSrc.Contains("regular")) return GeocacheSize.Regular;
            else if (imgSrc.Contains("large")) return GeocacheSize.Large;
            return null;
        }

        private float ReadStarInsideSpan(HTMLSpanElement difficultySpan)
        {
            var collection = difficultySpan.getElementsByTagName("img");
            if (collection.length != 1) throw new Exception();
            HTMLImg img = (HTMLImg)collection.item(0);
            string text = img.src;
            return ReadStar(img.href);
        }

        private float ReadStar(string imgSrc)
        {
            imgSrc = imgSrc.ToLower();
            if (imgSrc.Contains("1.gif")) return 1.0f;
            else if (imgSrc.Contains("1_5.gif")) return 1.5f;
            else if (imgSrc.Contains("2.gif")) return 2f;
            else if (imgSrc.Contains("2_5.gif")) return 2.5f;
            else if (imgSrc.Contains("3.gif")) return 3.0f;
            else if (imgSrc.Contains("3_5.gif")) return 3.5f;
            else if (imgSrc.Contains("4.gif")) return 4.0f;
            else if (imgSrc.Contains("4_5.gif")) return 4.5f;
            else if (imgSrc.Contains("5.gif")) return 5.0f;
            return 0;
        }

        #endregion ReadElement

        private bool IsNotLogged(HTMLDocument doc)
        {
            var obj = doc.getElementsByTagName("p");
            for (int i = 0; i < obj.length; i++)
            {
                mshtml.HTMLParaElement p = obj.item(i);

                if (p.className == "NotSignedInText") return true;
            }
            return false;
        }
    }
}